package com.qingcloud.adminbackend.command;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.crypto.SecureUtil;
import groovy.lang.GroovyClassLoader;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 源码class实例容器
 *
 * @author wangjinnan
 */
@Slf4j
public class ComponentInstanceContainer {

    private ComponentInstanceContainer() {
    }

    /**
     * 源码类加载器
     */
    private static final GroovyClassLoader GROOVY_CLASS_LOADER = new GroovyClassLoader();

    /**
     * 源码缓存，key是源码md5，value是源码内容
     */
    private static final ConcurrentMap<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();

    /**
     * 组件上级类加载器缓存
     */
    private static final ConcurrentMap<Integer, ClassLoader> COMPONENT_PARENT_CLASS_LOADER = new ConcurrentHashMap<>();

    /**
     * 组件依赖缓存
     */
    private static final ConcurrentMap<Integer, List<String>> COMPONENT_DEPENDENCIES = new ConcurrentHashMap<>();

    /**
     * 组件实例对象缓存，key是实例ID，value是实例对象
     */
    private static final ConcurrentMap<Integer, Object> INSTANCE_CACHE = new ConcurrentHashMap<>();

    /**
     * 创建采集器组件实例对象
     *
     * @param id             源码ID
     * @param sourceCode     组件实例源码
     * @param dependencies   所有依赖路径
     * @param interfaceClass 检查源码是否实现了某个类
     * @return 返回null，则说明创建没有问题；否则，返回的就是错误信息
     */
    public static Object buildSourceInstance(Integer id, String sourceCode, List<String> dependencies, Class<?> interfaceClass) {
        Object instanceObject = INSTANCE_CACHE.get(id);
        //已经创建过对象实例
        if (instanceObject != null) {
            return instanceObject;
        }
        try {
            //编译源码
            Class<?> componentClass = loadClass(id, sourceCode, dependencies);

            //获取组件实例对象
            if (Objects.nonNull(componentClass)) {
                Object object = componentClass.getDeclaredConstructor().newInstance();
                if (Objects.nonNull(interfaceClass) && interfaceClass.isInstance(object)) {
                    instanceObject = object;
                    INSTANCE_CACHE.put(id, instanceObject);
                    return instanceObject;
                }
                if (Objects.nonNull(interfaceClass)) {
                    log.error("source code not implement {}", interfaceClass.getName());
                } else {
                    log.error("parameter interfaceClass is null");
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return instanceObject;
    }

    /**
     * 查询实例对象
     *
     * @param id 源码ID
     */
    public static Object getInstance(Integer id) {
        return INSTANCE_CACHE.get(id);
    }

    /**
     * 移除实例对象
     *
     * @param id 源码ID
     */
    public static void removeInstance(Integer id) {
        INSTANCE_CACHE.remove(id);
    }

    /**
     * 获取所有的class文件
     *
     * @param jarPath 文件路径
     */
    private static List<String> getAllArchivesFromJar(String jarPath) {
        //遍历class文件
        List<String> archivesList = new ArrayList<>();
        //加载jar文件
        try (JarFile jarFile = new JarFile(jarPath)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                String fullName = entries.nextElement().getName();
                if (fullName.endsWith(".class")) {
                    archivesList.add(fullName.replace("/", ".").substring(0, fullName.length() - 6));
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return archivesList;
    }

    /**
     * 从lib jar中
     *
     * @param componentId    组件ID
     * @param jarPath        jar文件完整路径
     * @param dependencies   所有依赖绝对路径
     * @param interfaceClass 实现的接口或抽象类
     * @return 错误信息，没有错误返回null
     */
    public static Object loadLibInstance(Integer componentId, String jarPath,
                                         List<String> dependencies, Class<?> interfaceClass) {
        //组件对象
        Object instanceObject = null;
        //获取jar包中所有资源
        List<String> archivesList = getAllArchivesFromJar(jarPath);
        ClassLoader parentClassLoader;
        try {
            //获取父类加载器
            parentClassLoader = getParentClassLoader(componentId, dependencies);
            //加载lib jar包
            try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:" + jarPath)},
                    parentClassLoader)) {
                for (String archives : archivesList) {
                    try {
                        Class<?> newClass = urlClassLoader.loadClass(archives);
                        if (Objects.nonNull(newClass)) {
                            Object object = newClass.getDeclaredConstructor().newInstance();
                            if (interfaceClass.isInstance(object)) {
                                instanceObject = object;
                            }
                        }
                    } catch (Exception exception) {
                        log.warn("load class {} error {} {}", archives, exception.getClass().getName(), exception.getMessage());
                    }
                }
                if (Objects.isNull(instanceObject)) {
                    log.warn("没有从{}中找到实现类{}", jarPath, interfaceClass.getName());
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return instanceObject;
    }

    /**
     * 通过源码加载类
     *
     * @param id           源码ID
     * @param sourceCode   源码字符串
     * @param dependencies 依赖文件路径
     * @return 返回加载的类
     */
    private static Class<?> loadClass(Integer id, String sourceCode, List<String> dependencies) {
        ClassLoader parentClassLoader;
        GroovyClassLoader groovyClassLoader = null;
        try {
            //获取源码md5
            String sourceMd5 = SecureUtil.md5(sourceCode);

            //从缓存中查找，防止重复加载
            Class<?> clazz = CLASS_CACHE.get(sourceMd5);
            if (Objects.isNull(clazz)) {
                //获取父类加载器
                parentClassLoader = getParentClassLoader(id, dependencies);
                groovyClassLoader = new GroovyClassLoader(parentClassLoader);
                clazz = groovyClassLoader.parseClass(sourceCode);
                CLASS_CACHE.putIfAbsent(sourceMd5, clazz);
                groovyClassLoader.close();
            }
            return clazz;
        } catch (Exception e) {
            return GROOVY_CLASS_LOADER.parseClass(sourceCode);
        } finally {
            try {
                if (groovyClassLoader != null) {
                    groovyClassLoader.close();
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 获取一个组件的父类加载器，一般组件的依赖都是固定不变的，依赖交给组件父类加载器
     * 如果组件依赖变化，需要新建一个组件
     *
     * @param id           源码ID
     * @param dependencies 依赖文件路径
     * @return 父类加载器
     */
    private static ClassLoader getParentClassLoader(Integer id, List<String> dependencies) throws MalformedURLException {
        ClassLoader classLoader = COMPONENT_PARENT_CLASS_LOADER.get(id);
        if (classLoader == null) {
            if (dependencies != null && !dependencies.isEmpty()) {
                //初始化有依赖的父类加载器
                int size = dependencies.size();
                URL[] urls = new URL[size];
                for (int i = 0; i < dependencies.size(); i++) {
                    log.info("will load jar {}", dependencies.get(i));
                    urls[i] = new URL("file:" + dependencies.get(i));
                }
                classLoader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
                COMPONENT_DEPENDENCIES.put(id, dependencies);
            } else {
                classLoader = Thread.currentThread().getContextClassLoader();
            }
            COMPONENT_PARENT_CLASS_LOADER.put(id, classLoader);
        } else {
            //检查是否有新增依赖
            if (CollUtil.isNotEmpty(dependencies)) {
                final List<String> oldDependencies = COMPONENT_DEPENDENCIES.getOrDefault(id, new ArrayList<>());
                dependencies.removeAll(oldDependencies);
                if (CollUtil.isNotEmpty(dependencies)) {
                    if (classLoader instanceof URLClassLoader) {
                        //如果原来是URLClassLoader，增加新的依赖
                        addNewDependencies(dependencies, (URLClassLoader) classLoader);
                    } else {
                        //初始化有依赖的父类加载器
                        int size = dependencies.size();
                        URL[] urls = new URL[size];
                        for (int i = 0; i < dependencies.size(); i++) {
                            log.info("will load jar {}", dependencies.get(i));
                            urls[i] = new URL("file:" + dependencies.get(i));
                        }
                        classLoader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
                        COMPONENT_DEPENDENCIES.put(id, dependencies);
                    }
                }
            }
        }
        return classLoader;
    }

    /**
     * 主class loader中加载jar包
     */
    private static void addNewDependencies(List<String> jarPathList, URLClassLoader classLoader) {
        //从URLClassLoader类加载器中获取类的addURL方法
        Method method = null;
        try {
            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        } catch (NoSuchMethodException | SecurityException e1) {
            log.error(e1.getMessage(), e1);
        }
        if (Objects.nonNull(method)) {
            // 获取方法的访问权限
            boolean accessible = method.isAccessible();
            try {
                //修改访问权限为可写
                if (!accessible) {
                    method.setAccessible(true);
                }
                for (String jarPath : jarPathList) {
                    File jarFile = new File(jarPath);
                    //获取jar文件的url路径
                    URL url = jarFile.toURI().toURL();
                    //jar路径加入到系统url路径里
                    method.invoke(classLoader, url);
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                method.setAccessible(accessible);
            }
        }
    }

}
